package com.woniuxy.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.woniuxy.common.result.Result;
import com.woniuxy.common.result.enums.IErrorCode;
import com.woniuxy.common.result.enums.impl.BusinessCode;
import com.woniuxy.common.utils.JwtTemplate;
import com.woniuxy.gateway.config.IgnorePathConfig;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.List;

/**
 * 一、网关全局过滤器，实现统一鉴权：拦截所有的请求
 *   1、继承 GlobalFilter
 *   2、重写 filter() 方法
 *   3、chain.filter(exchange); 放行
 *
 * 二、网关、网关过滤 都是转发拦截操作、这里不是处理业务逻辑
 *
 * 三、Mono<Void>、ServerWebExchange、GatewayFilterChain 这写api是
 *    reactive 中的技术 (和 springmvc不一样的 了解就可以)
 *
 *  token 存储:
 *   前端:
 *         localStore  html中
 *         Vuex、Pinia Vue 中
 *   后端:
 *         对象内存 这个    堆栈里面
 *         Threadlocal     存储 如果是多线程 实现共一个线程数据共享 推荐这个
 *         Redis           最好 ( ttl、多个微服务可以实现数据共享 )
 *         mysql           也可以 时间操作不方便
 */
@Configuration
public class AuthFilter implements GlobalFilter {

    @Autowired
    private IgnorePathConfig ignorePathConfig;

    @Autowired
    private JwtTemplate jwtTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public static final String AUTHORIZATION = "Authorization";
    public static final String BEARER = "Bearer ";

    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //写业务逻辑
        //1、拿到请求路径
        //请求对象
        ServerHttpRequest request = exchange.getRequest();
        //响应对象
        ServerHttpResponse response = exchange.getResponse();

        //拿到请求路径
        String path = request.getURI().getPath();
        System.out.println("path = " + path);

        //把path 和白名单路径进行比较
        List<String> whiteList = ignorePathConfig.getWhiteList();

        //通过表达式 判断 路径问题
        AntPathMatcher pathMatcher = new AntPathMatcher();

        //stream
//        for (String whPath : whiteList) {
//            if (pathMatcher.match(whPath,path)){  //path和白名单是否可以匹配
//                return chain.filter(exchange); //放行
//            }
//        }
        boolean flag = whiteList.stream().anyMatch(urlPatter -> pathMatcher.match(urlPatter,path));
        if (flag) {
            // 放行，不进行token校验
            return chain.filter(exchange);
        }
        //02  获取token  前端 发送数据 token:    Authorization=Bearer token
        String authToken = request.getHeaders().getFirst(AUTHORIZATION);
        if (StringUtils.isEmpty(authToken)){
            return responseError(response, BusinessCode.USER_NOT_LOGGED_IN);
        }
        String token = authToken.replaceAll(BEARER, "");
        //03 解析
        boolean verify = jwtTemplate.verify(token); //校验
        Object userId = null;
        if (verify){
             userId = jwtTemplate.parseToken(token, "userId");
        }

        //04 解析userId 判断token是否过期没有 （查询redis中是否过期了）
        String redisKey = "user:token:" + userId.toString();
        String redisToken = stringRedisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isEmpty(redisToken)){
            return responseError(response, BusinessCode.USER_VERIFY_TIMEOUT);
        }

        //05、续期
//        stringRedisTemplate.opsForValue().set(redisKey,token, Duration.ofMinutes(30));
        stringRedisTemplate.expire(
                redisKey,
                Duration.ofMinutes(30)
        );

        //06 网关中重写请求头，让请求头携带userId，再转发到微服务，这样在微服务中就可以直接获取登录用户id
        request.mutate().header("userId",userId.toString());

        //7、微服务直接从请求头获取用户id
        return chain.filter(exchange);
    }

    private static Mono<Void> responseError(ServerHttpResponse response, IErrorCode resultCode) throws JsonProcessingException {
        // 返回错误
        Result<Object> result = Result.fail(resultCode);
        //byte 数组
        byte[] bytes = new ObjectMapper().writeValueAsBytes(result);
        //数据缓冲
        DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
        //通过数组缓冲生成  Mono
        // response 回写 Mono
        return response.writeWith(Mono.just(dataBuffer));
    }
}